Skip to content

feat(sdk): add Go SDK foundation and core clients (1/4)#9

Open
rhuss wants to merge 14 commits into
mainfrom
go-sdk-1-foundation
Open

feat(sdk): add Go SDK foundation and core clients (1/4)#9
rhuss wants to merge 14 commits into
mainfrom
go-sdk-1-foundation

Conversation

@rhuss

@rhuss rhuss commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

Add the Go SDK module with proto bindings, domain types, internal converters, and all core API clients.

Part 1 of 4 for #2044. Review this PR first.

What's Included

  • Module setup: go.mod, go.sum, Makefile, mise.toml under sdk/go/
  • Proto bindings: 3 proto files + generated .pb.go (committed, no protoc needed)
  • Domain types: types/ package with all SDK structs, errors, options, interfaces
  • Internal layer: internal/converter/ (proto-to-SDK conversion) and internal/grpc/ (connection management)
  • Core clients: sandbox, provider, service, exec, file, health, SSH, TCP, policy, config, profile, refresh
  • Auth primitives: AuthProvider interface, token refresh wrapper, extra-headers decorator

File Tree

sdk/go/
├── go.mod, go.sum, Makefile, mise.toml
├── proto/                          # Proto definitions + generated .pb.go
├── openshell/v1/
│   ├── types/                      # Domain types, errors, options
│   ├── internal/converter/         # Proto ↔ SDK type conversion
│   ├── internal/grpc/              # gRPC connection management
│   ├── client.go                   # Top-level Client with sub-clients
│   ├── *_client.go + *_client_test.go  # API clients + tests
│   └── auth*.go                    # Auth primitives

117 files | Module: github.com/NVIDIA/OpenShell/sdk/go

PR Chain

  1. This PR - Foundation + core clients (117 files)
  2. Gateway integration, auth, fakes, edge client (70 files)
  3. Fern documentation + CI job (7 files)
  4. Design specifications (79 files)

Summary by CodeRabbit

  • New Features
    • Added a comprehensive Go SDK for OpenShell v1 with a unified client and sub-APIs for health, sandboxes, services, providers/profiles, configuration, policy management, exec (streaming/interactive), files, SSH sessions/tunneling, TCP forwarding/listening, watches/logs.
    • Added authentication helpers (no auth, static bearer, extra headers, and refreshable OAuth2 tokens) plus typed error codes/predicates and runnable usage examples.
  • Tests
    • Added extensive unit test coverage across the new SDK surfaces, plus build-tagged integration health checks.
  • Chores
    • Added standardized Go development and protobuf generation/verification workflows.

rhuss added 4 commits July 11, 2026 08:37
Parent brainstorm (01) defines the layered measurement approach for
evaluating Agent Sandbox warm pooling on OpenShift. Child documents
cover cluster setup (02), measurements (03), and results synthesis (04).

Assisted-By: 🤖 Claude Code
Assisted-By: 🤖 Claude Code
Signed-off-by: Roland Huß <rhuss@redhat.com>
Assisted-By: 🤖 Claude Code
Signed-off-by: Roland Huß <rhuss@redhat.com>
Add the Go SDK module with proto bindings, domain types, internal
converters, and all core API clients (sandbox, provider, service,
exec, file, health, SSH, TCP, policy).

Module: github.com/NVIDIA/OpenShell/sdk/go

Part 1 of 4 for issue NVIDIA#2044.
Assisted-By: 🤖 Claude Code

Signed-off-by: Roland Huß <rhuss@redhat.com>
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rhuss, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e8ca9b2b-f72e-4381-bcf7-633e85ee7378

📥 Commits

Reviewing files that changed from the base of the PR and between c386962 and 5e3abe0.

📒 Files selected for processing (2)
  • sdk/go/openshell/v1/example_fake_test.go
  • sdk/go/openshell/v1/example_test.go
📝 Walkthrough

Walkthrough

Changes

The pull request adds a complete Go v1 SDK module with protobuf contracts, shared types, authentication, resource clients, conversion helpers, streaming execution and networking, tests, examples, integration scaffolding, documentation checks, and protobuf generation tasks.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant SDKClient
  participant Converter
  participant Gateway
  participant Stream
  Caller->>SDKClient: create client or call resource API
  SDKClient->>Converter: convert SDK request
  SDKClient->>Gateway: invoke RPC
  Gateway-->>SDKClient: return response or stream
  SDKClient->>Converter: convert response
  SDKClient-->>Caller: return SDK result
  SDKClient->>Stream: exchange exec or network frames
Loading

Possibly related issues

  • rhuss/openshell-sdk-go#18 — The SDK adds TCP forwarding APIs but does not implement reverse port forwarding (RemoteListen) or its protocol extensions.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.40% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing the Go SDK foundation and core clients as part 1/4.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch go-sdk-1-foundation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@rhuss
rhuss marked this pull request as ready for review July 11, 2026 10:43
@rhuss
rhuss requested a review from Copilot July 11, 2026 10:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

devin-ai-integration[bot]

This comment was marked as resolved.

Applied fixes from bot review comments:
- Comment #3563985966: fix goroutine leak in interactiveSession readLoop
- Comment #3563985982: add negative pagination validation in providerClient.List

Assisted-By: 🤖 Claude Code
Signed-off-by: Roland Huß <rhuss@redhat.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Applied fixes from bot review comments:
- Comment #3563996825: make interactiveSession.ExitCode() idempotent
- Comment #3563996843: map FailedPrecondition to ErrorConflict

Assisted-By: 🤖 Claude Code
Signed-off-by: Roland Huß <rhuss@redhat.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Applied fixes from bot review comments:
- Comment #3564009036: acquire sendMu in interactiveSession.Close()

Assisted-By: 🤖 Claude Code
Signed-off-by: Roland Huß <rhuss@redhat.com>
devin-ai-integration[bot]

This comment was marked as resolved.

Applied fixes from bot review comments:
- Comment #3564015967: wait for readLoop goroutine in Close()

Assisted-By: 🤖 Claude Code
Signed-off-by: Roland Huß <rhuss@redhat.com>
coderabbitai[bot]

This comment was marked as resolved.

Applied fixes from bot review comments:
- Comment #3564038912: guard nil token from TokenSource in auth refresh
- Comment #3564038916: map codes.Unauthenticated in gRPC error converter
- Comment #3564038918: use hasExit flag instead of -1 sentinel in ExecResultFromEvents
- Comment #3564038920: use append for nil-safe proto repeated field conversion
- Comment #3564038923: fix incorrect timestamp comment in time_test.go
- Comment #3564038931: reject partial cert/key mTLS configuration
- Comment #3564038941: add 30s timeout to revoke cleanup calls

Assisted-By: 🤖 Claude Code
devin-ai-integration[bot]

This comment was marked as resolved.

rhuss added 2 commits July 11, 2026 14:17
Applied fixes from bot review comments:
- Comment #3564088995: add zero-length buffer guard to interactiveSession.Read

Assisted-By: 🤖 Claude Code
Lower the minimum Go version to 1.24.0 for broader compatibility.
Downgraded dependencies:
- google.golang.org/grpc v1.81.1 -> v1.80.0
- golang.org/x/oauth2 v0.36.0 -> v0.35.0
- google.golang.org/genproto v20260226 -> v20260120
- golang.org/x/net v0.51.0 -> v0.49.0 (indirect)
- golang.org/x/sys v0.42.0 -> v0.41.0 (indirect)
- golang.org/x/text v0.34.0 -> v0.33.0 (indirect)

All 250 unit tests pass. Build succeeds.

Assisted-By: 🤖 Claude Code
rhuss added a commit that referenced this pull request Jul 11, 2026
Lower the minimum Go version to 1.24.0, matching PR #9.

Assisted-By: 🤖 Claude Code
rhuss added a commit that referenced this pull request Jul 11, 2026
Lower the minimum Go version to 1.24.0, matching PR #9.

Assisted-By: 🤖 Claude Code
rhuss added a commit that referenced this pull request Jul 11, 2026
Lower the minimum Go version to 1.24.0, matching PR #9.

Assisted-By: 🤖 Claude Code
rhuss added a commit that referenced this pull request Jul 11, 2026
Lower the minimum Go version to 1.24.0, matching PR #9.

Assisted-By: 🤖 Claude Code
rhuss added a commit that referenced this pull request Jul 11, 2026
Lower the minimum Go version to 1.24.0, matching PR #9.

Assisted-By: 🤖 Claude Code
rhuss added a commit that referenced this pull request Jul 11, 2026
Lower the minimum Go version to 1.24.0, matching PR #9.

Assisted-By: 🤖 Claude Code
rhuss added a commit that referenced this pull request Jul 11, 2026
Lower the minimum Go version to 1.24.0, matching PR #9.

Assisted-By: 🤖 Claude Code
rhuss added a commit that referenced this pull request Jul 11, 2026
Lower the minimum Go version to 1.24.0, matching PR #9.

Assisted-By: 🤖 Claude Code
rhuss added a commit that referenced this pull request Jul 11, 2026
Lower the minimum Go version to 1.24.0, matching PR #9.

Assisted-By: 🤖 Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
sdk/go/go.mod (1)

3-5: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Validate Go 1.24 compatibility under the pinned toolchain.

go 1.24.0 declares the minimum version, but toolchain go1.26.4 makes Go automatically select or download Go 1.26.4 by default. Consequently, the module’s normal build/test path does not verify Go 1.24 compatibility. Either align/remove the toolchain directive, or add CI coverage using GOTOOLCHAIN=local with Go 1.24. The Go documentation distinguishes these directives and documents this automatic switching behavior. (go.dev)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/go/go.mod` around lines 3 - 5, Update the Go module/toolchain
configuration around the go and toolchain directives so the normal validation
path actually tests Go 1.24 compatibility. Either align or remove toolchain
go1.26.4, or add CI coverage that runs with Go 1.24 and GOTOOLCHAIN=local;
preserve newer-toolchain support separately if required.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@sdk/go/go.mod`:
- Around line 3-5: Update the Go module/toolchain configuration around the go
and toolchain directives so the normal validation path actually tests Go 1.24
compatibility. Either align or remove toolchain go1.26.4, or add CI coverage
that runs with Go 1.24 and GOTOOLCHAIN=local; preserve newer-toolchain support
separately if required.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14f5bfdc-f5a9-4648-9736-5e6e81f095e4

📥 Commits

Reviewing files that changed from the base of the PR and between 3519028 and 642916d.

⛔ Files ignored due to path filters (1)
  • sdk/go/go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • sdk/go/go.mod
  • sdk/go/openshell/v1/exec_client.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • sdk/go/openshell/v1/exec_client.go

devin-ai-integration[bot]

This comment was marked as resolved.

Applied fixes from bot review comments:
- Comment #3564256792: derive interactive session context from caller's ctx to prevent goroutine leak

Assisted-By: 🤖 Claude Code

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 5 additional findings in Devin Review.

Open in Devin Review

opt = &opts[0]
}

stream, err := e.client.ExecSandboxInteractive(ctx)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Closing an interactive command session can hang forever because the underlying connection is not cancellable

The interactive session's network stream is opened with the caller's original context (e.client.ExecSandboxInteractive(ctx) at sdk/go/openshell/v1/exec_client.go:99) instead of a cancellable child context, so when Close() cancels its internal context (sdk/go/openshell/v1/exec_client.go:325), the receive loop remains blocked on the uncancellable stream and Close() waits on it forever (<-s.done at sdk/go/openshell/v1/exec_client.go:326).

Impact: Calling Close() on an interactive exec session can block indefinitely, leaking the goroutine and the gRPC stream.

Mechanism: stream context not derived from cancellable child

The sibling methods Stream() (exec_client.go:75-76) and Forward() (tcp_client.go:51-52) both follow the correct pattern:

streamCtx, cancel := context.WithCancel(ctx)
stream, err := e.client.ExecSandbox(streamCtx, req)

This ensures that calling cancel() in Close() terminates the gRPC stream, causing Recv() to return a context error and unblocking the read loop.

However, Interactive() at exec_client.go:99 passes the raw parent ctx:

stream, err := e.client.ExecSandboxInteractive(ctx)

Then newInteractiveSession at exec_client.go:194 creates a separate child context that is never wired to the stream:

ctx, cancel := context.WithCancel(parentCtx)

When Close() calls s.cancel() at line 325, it cancels this orphan child context, but the stream's Recv() is governed by the original parent context and keeps blocking. Close() then blocks on <-s.done at line 326 until either the server closes its side or the parent context is cancelled externally.

The fix is to create the stream with a child context, matching the pattern used by Stream() and Forward().

Prompt for agents
In exec_client.go, the Interactive() method at line 99 creates the gRPC stream with the parent context ctx, but it should use a cancellable child context so that Close() can terminate the stream. The fix should mirror the pattern used by Stream() (lines 75-76) and Forward() in tcp_client.go (lines 51-52):

1. In Interactive(), before calling e.client.ExecSandboxInteractive, create a child context: streamCtx, streamCancel := context.WithCancel(ctx)
2. Pass streamCtx to ExecSandboxInteractive instead of ctx
3. On error from ExecSandboxInteractive, call streamCancel() before returning
4. Pass streamCancel (or streamCtx) to newInteractiveSession instead of creating a second child context inside newInteractiveSession
5. In newInteractiveSession, use the provided cancel function and context instead of creating new ones from parentCtx

This ensures that when Close() calls s.cancel(), it cancels the context that governs the stream, causing Recv() to return and unblocking the readLoop goroutine.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

rhuss added 2 commits July 12, 2026 08:52
- Make tcpForwardConn.Close idempotent with sync.Once, matching sibling types
- Guard against nil token dereference in refreshableAuth.GetRequestMetadata
- Add mutex locks to all mockSandboxServer methods for race safety
- Map codes.Unauthenticated to ErrorPermissionDenied in gRPC error converter
- Replace exitCode == -1 sentinel with explicit hasExit boolean flag

Assisted-By: 🤖 Claude Code
Signed-off-by: Roland Huß <rhuss@redhat.com>
The example_test.go and example_fake_test.go files import the fake/
package which arrives in PR 2/3. Add a //go:build fakeclient constraint
so PR 1/3 builds and tests cleanly on its own. The tag will be removed
when the fake package lands.

Assisted-By: 🤖 Claude Code
Signed-off-by: Roland Huß <rhuss@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants